#ifndef _NE_MATRIX3_H_
#define _NE_MATRIX3_H_

#include <mathematics/Vector3.h>

namespace ne
{
    /*
        Matrix3 is the stander math matrix
        v' = v * Matrix3;

        TODO: The Vector or Matrix at the left of '*' will be rotated by
        Matrix at the right
    */

    class NEAPI Matrix3
    {
        public:
            inline Matrix3()
            {
                mData[0][0] = 1.0f; mData[0][1] = 0.0f; mData[0][2] = 0.0f;
                mData[1][0] = 0.0f; mData[1][1] = 1.0f; mData[1][2] = 0.0f;
                mData[2][0] = 0.0f; mData[2][1] = 0.0f; mData[2][2] = 1.0f;
            }

            inline Matrix3(const Matrix3 &m)
            {
                memcpy(mData, m.mData, 9 * sizeof(real));
            }

            inline explicit Matrix3(const real m[3][3])
            {
                memcpy(mData, m, 9 * sizeof(real));
            }

            inline explicit Matrix3(const real m[9])
            {
                memcpy(mData, m, 9 * sizeof(real));
            }

            inline Matrix3(const real m00, const real m01, const real m02,
                           const real m10, const real m11, const real m12,
                           const real m20, const real m21, const real m22)
            {
                mData[0][0] = m00; mData[0][1] = m01; mData[0][2] = m02;
                mData[1][0] = m10; mData[1][1] = m11; mData[1][2] = m12;
                mData[2][0] = m20; mData[2][1] = m21; mData[2][2] = m22;
            }

            inline void swap(Matrix3 &m)
            {
                std::swap(mData[0][0], m.mData[0][0]);
                std::swap(mData[0][1], m.mData[0][1]);
                std::swap(mData[0][2], m.mData[0][2]);
                std::swap(mData[1][0], m.mData[1][0]);
                std::swap(mData[1][1], m.mData[1][1]);
                std::swap(mData[1][2], m.mData[1][2]);
                std::swap(mData[2][0], m.mData[2][0]);
                std::swap(mData[2][1], m.mData[2][1]);
                std::swap(mData[2][2], m.mData[2][2]);
            }

            inline void setRow(const size_t r, const Vector3 &v)
            {
                assert(r < 3);
                mData[r][0] = v.x;
                mData[r][1] = v.y;
                mData[r][2] = v.z;
            }

            inline Vector3 getRow(const size_t r) const
            {
                assert(r < 3);
                return Vector3(mData[r][0], mData[r][1], mData[r][2]);
            }

            inline void setColumn(const size_t c, const Vector3 &v)
            {
                assert(c < 3);
                mData[0][c] = v.x;
                mData[1][c] = v.y;
                mData[2][c] = v.z;
            }

            inline Vector3 getColumn(const size_t c) const
            {
                assert(c < 3);
                return Vector3(mData[0][c], mData[1][c], mData[2][c]);
            }

            inline real* operator [] (const size_t v)
            {
                assert(v < 3);
                return mData[v];
            }

            inline const real* operator [] (const size_t v) const
            {
                assert(v < 3);
                return mData[v];
            }

            inline real* ptr()
            {
                return mData[0];
            }

            inline const real* ptr() const
            {
                return mData[0];
            }

            inline Matrix3& operator = (const Matrix3 &m)
            {
                memcpy(mData, m.mData, 9 * sizeof(real));
                return *this;
            }

            inline void identity()
            {
                mData[0][0] = 1.0f; mData[0][1] = 0.0f; mData[0][2] = 0.0f;
                mData[1][0] = 0.0f; mData[1][1] = 1.0f; mData[1][2] = 0.0f;
                mData[2][0] = 0.0f; mData[2][1] = 0.0f; mData[2][2] = 1.0f;
            }

            bool operator == (const Matrix3 &m) const;
            bool operator != (const Matrix3 &m) const;

            Matrix3 operator + (const Matrix3 &m) const;
            Matrix3 operator - (const Matrix3 &m) const;
            // M * m != m * M
            Matrix3 operator * (const Matrix3 &m) const;

            // -M
            Matrix3 operator - () const;

            // M * V
            Vector3 operator * (const Vector3 &v) const;
            // TODO: Use this to rotate Vector3
            // V * M    (V * M != M * V)
            NEAPI friend Vector3 operator * (const Vector3 &v, const Matrix3 &m);

            Matrix3 operator * (const real f) const;
            NEAPI friend Matrix3 operator * (const real f, const Matrix3 &m);

            // m = T[M];
            void transpose(Matrix3 &m) const;
            // T[M]
            Matrix3 transpose() const;

            // m = adj(M)
            void adjoint(Matrix3 &m) const;
            // adj(M)
            Matrix3 adjoint() const;

            // m = 1 / M
            bool inverse(Matrix3 &m) const;
            // 1 / M
            Matrix3 inverse() const;

            // |M|
            real determinant() const;

            void orthonormalize();

            void fromAxes(const Vector3 &vX, const Vector3 &vY, const Vector3 &vZ);
            void toAxes(Vector3 &vX, Vector3 &vY, Vector3 &vZ) const;

            // The matrix must be orthonormal.
            void fromAxisAngle(const Vector3 &axis, const real a);
            bool toAxisAngle(Vector3 &axis, real &a) const;

            // The matrix must be orthonormal.
            void fromEulerAngles(const real yaw, const real pitch, const real roll);
            bool toEulerAngles(real &yaw, real &pitch, real &roll) const;

            void fromAxisXAngle(const real a);
            void fromAxisYAngle(const real a);
            void fromAxisZAngle(const real a);

            void fromScaleAxisRate(const Vector3 &axis, const real rate);

            void fromOrthoProjectionNormal(const Vector3 &norm);

            void fromReflectionNormal(const Vector3 &norm);

            NEAPI friend std::ostream& operator << (std::ostream &o, const Matrix3 &m);

            inline ~Matrix3() {}

        public:
            static const Matrix3 ZERO;
            static const Matrix3 IDENTITY;

        private:
            real mData[3][3];
    };
}

#endif
